﻿using System;
using System.Data.SqlClient;

namespace PropertyExpression.ControllableQuery
{
    public static class QueryContextExtensions2
    {
        public static IParam<T> CreateParam<T>(this SqlCommand it, Func<string, SqlParameter> sqlParameterFunc, Func<T, object> toSqlParameterValueFunc)
        {
            return new Param<T>(it, sqlParameterFunc, toSqlParameterValueFunc);
        }

        private class Param<T> : IParam<T>
        {
            private readonly SqlCommand command;
            private readonly Lazy onToSql;
            private OptionalValue<string> name = OptionalValue<string>.Nothing;
            private OptionalValue<Func<T>> valueFunc = OptionalValue<Func<T>>.Nothing;

            public Param(SqlCommand command, Func<string, SqlParameter> sqlParameterFunc, Func<T, object> toSqlParameterValueFunc)
            {
                this.command = command;

                onToSql = new Lazy(
                    () => {
                        var sqlParameter = sqlParameterFunc(GetName());
                        if (valueFunc.HasValue())
                            sqlParameter.Value = toSqlParameterValueFunc(valueFunc.ValueOrEx()());
                        valueFuncChanged = _ => sqlParameter.Value = toSqlParameterValueFunc(_());
                        nameChanged = _ => sqlParameter.ParameterName = _;
                        command.Parameters.Add(sqlParameter);
                    });
            }

            public string Name
            {
                set
                {
                    name = new OptionalValue<string>(value);
                    nameChanged(value);
                }
            }

            private Action<string> nameChanged = delegate { };

            public T Value
            {
                set { ValueFunc = () => value; }
            }

            public Func<T> ValueFunc
            {
                set
                {
                    valueFunc = new OptionalValue<Func<T>>(value);
                    valueFuncChanged(value);
                }
            }

            private Action<Func<T>> valueFuncChanged = delegate { };

            public string ToSql()
            {
                onToSql.Do();
                return GetName();
            }

            private string GetName()
            {
                if (!name.HasValue())
                {
                    name = new OptionalValue<string>(SqlParameterNameGenerator.GetNextName(command.Parameters.Count));
                }
                return name.ValueOrEx();
            }
        }        
    }

    internal class OptionalValue<TValue>
    {
        public readonly static OptionalValue<TValue> Nothing = new OptionalValue<TValue>();

        private readonly bool hasValue;
        private readonly TValue value;

        private OptionalValue()
        {
        }

        public OptionalValue(TValue value)
        {
            this.value = value;
            hasValue = true;
        }

        public bool HasValue()
        {
            return hasValue;
        }

        public TValue ValueOrEx()
        {
            if (!hasValue) throw new InvalidOperationException();

            return value;
        }
    }

    internal class Lazy
    {
        private readonly Action action;
        private bool isDone;

        public Lazy(Action action)
        {
            this.action = action;
        }

        public void Do()
        {
            if (isDone) return;

            action();
            isDone = true;
        }
    }
}